#ifndef PREF_WIDGETS_H_
#define PREF_WIDGETS_H_

#include <gtkmm.h>
#include <gtk/gtk.h>

#include "../conf.h"
#include "../yellowpage.h"
#include "../widgets.h"
#include "../menu.h"

#include "../gtkversion.h"

namespace gpyp {
namespace pref {

class FileEntry : public Gtk::Entry {
protected:
  virtual void on_drag_data_received(const Glib::RefPtr<Gdk::DragContext> &context,
                                     int x, int y,
                                     const Gtk::SelectionData &selection_data,
                                     guint info, guint time);

public:
  FileEntry();
};

class SoundBox : public Gtk::HBox {
private:
  Gtk::Window              &parent_;

  conf::RefAttributeStr     sound_;
  conf::RefAttributeDouble  volume_;
  sigc::connection          conn_volume_on_changed_;

  FileEntry                 sound_entry_;
  Gtk::Button               open_button_;
  Gtk::ToggleButton         play_button_;
#ifdef GTK_HAVE_VOLUME_BUTTON
  GtkVolumeButton          *volume_button_;
#else
  Gtk::SpinButton           volume_spin_;
#endif

  DialogWrapper<Gtk::FileChooserDialog> file_dialog_;

  uint play_id_;

  sigc::connection conn_play_button_toggled_;

private:
  void volume_on_changed();

  void open_button_on_clicked();
  void play_button_on_toggled();
#ifdef GTK_HAVE_VOLUME_BUTTON
  static void static_volume_button_on_value_changed(GtkScaleButton *button, gdouble value, gpointer user_data);
  void volume_button_on_value_changed(double value);
#else
  void volume_spin_on_value_changed();
#endif
  void file_dialog_on_response(int response_id);

protected:
  double get_volume();
  void set_volume(double volume);

  virtual bool sound_play();
  virtual void sound_stop();
  virtual void sound_on_error(const Glib::ustring &error_message);
  virtual void sound_on_complete();

public:
  void set(conf::RefAttributeStr sound, conf::RefAttributeDouble volume);
  void unset();

  SoundBox(Application &app, Gtk::Window &parent);
  virtual ~SoundBox();
};

class ColorComboBox : public Gtk::ComboBox {
private:
  Application                  &app_;
  Gtk::Window                  &parent_;
  bool                          none_;

  Gtk::TreeModelColumn<ConfMain::RefPresetColor> column_color_;
  Gtk::TreeModelColumn<bool>    column_separator_;
  Gtk::TreeModelColumnRecord    columns_;
  Glib::RefPtr<Gtk::ListStore>  liststore_;

  Gtk::CellRendererText         renderer_;

  Color                         custom_color_;
  Gtk::TreeIter                 iter_none_;
  Gtk::TreeIter                 iter_custom_;
  Gtk::TreeIter                 iter_separator_;

  bool                          block_changed_;
  RefAttributeColor             attr_;
  sigc::connection              conn_attr_changed_;

  DialogWrapper<Gtk::ColorSelectionDialog> custom_dialog_;

private:
  Gtk::TreeIter get_iter(Color color);
  Gtk::TreeIter get_iter(ConfMain::RefPresetColor color);
  ConfMain::RefPresetColor get_color(Gtk::TreeIter iter);

  void add_color(ConfMain::RefPresetColor preset_color);
  void remove_color(ConfMain::RefPresetColor preset_color);
  void clear();

  bool separator_func(const Glib::RefPtr<Gtk::TreeModel> &model,
                      const Gtk::TreeIter                &iter);
  void on_cell_data(const Gtk::TreeIter &iter);

  void init_custom_dialog();

  virtual void on_changed();
  void attr_on_changed();

  void custom_dialog_on_response(int response_id);

public:
  void set(RefAttributeColor attr);
  void unset();

  inline bool get_can_add() const {
    Gtk::TreeIter iter = get_active();
    return iter && iter == iter_custom_;
  }
  inline bool get_can_remove() const {
    Gtk::TreeIter iter = get_active();
    return iter && (!none_ || iter != iter_none_) && iter != iter_custom_;
  }

  void add_preset();
  void remove_preset();

  void show_custom_dialog();

  ColorComboBox(Application      &app,
                Gtk::Window      &parent,
                RefAttributeColor attr = RefAttributeColor(),
                bool              none = true);
  virtual ~ColorComboBox();
};

class ColorBox : public Gtk::HBox {
private:
  class ArrowButton : public Gtk::Button {
  private:
    Gtk::Arrow arrow_;

  public:
    ArrowButton();
  };

  ColorComboBox  combobox_;
  Gtk::Button    pick_color_;
  ArrowButton    button_;

  Gtk::Menu      menu_;
  SimpleMenuItem mi_add_;
  SimpleMenuItem mi_remove_;

  void calc_popup_menu_position(int &x, int &y, bool &push_in);
  void button_on_clicked();

  void combobox_on_changed();

public:
  void set(RefAttributeColor attr);
  void unset();

  ColorBox(Application      &app,
           Gtk::Window      &parent,
           RefAttributeColor attr = RefAttributeColor(),
           bool              none = true);
};

#ifndef GTK_HAVE_CELL_RENDERER_ACCEL
class CellRendererAccel : public Gtk::CellRendererText {
private:
  sigc::signal<void, const Glib::ustring &> signal_accel_cleared_;
  sigc::signal<void, const Glib::ustring &, guint, Gdk::ModifierType, guint> signal_accel_edited_;
public:
  inline sigc::signal<void, const Glib::ustring &> signal_accel_cleared() {
    return signal_accel_cleared_;
  }
  inline sigc::signal<void, const Glib::ustring &, guint, Gdk::ModifierType, guint> signal_accel_edited() {
    return signal_accel_edited_;
  }

private:
  class Dialog : public Gtk::Dialog {
  private:
    Gtk::Label  label_;
    Gtk::Button button_grab_;
    Gtk::Button button_clear_;
    Gtk::HBox   hbox_;

    bool changed_;
    bool cleared_;

    guint             accel_key_;
    Gdk::ModifierType accel_mods_;
    guint             hardware_keycode_;

    bool grabbing_;

    void update_label();

    void grab_on_clicked();
    void clear_on_clicked();

  protected:
    virtual bool on_key_press_event(GdkEventKey *event);

  public:
    inline bool changed() {
      return changed_;
    }
    inline bool cleared() {
      return cleared_;
    }

    inline guint accel_key() {
      return accel_key_;
    }
    inline Gdk::ModifierType accel_mods() {
      return accel_mods_;
    }
    inline guint hardware_keycode() {
      return hardware_keycode_;
    }

    Dialog(guint key, Gdk::ModifierType mods);
  };

  guint             accel_key_;
  Gdk::ModifierType accel_mods_;

protected:
  virtual Gtk::CellEditable *
  start_editing_vfunc(GdkEvent              *event,
                      Gtk::Widget           &widget,
                      const Glib::ustring   &path,
                      const Gdk::Rectangle  &background_area,
                      const Gdk::Rectangle  &cell_area,
                      Gtk::CellRendererState flags);

public:
  void set(guint key, Gdk::ModifierType mods);

  CellRendererAccel();
};
#endif

class CellRendererEncoding : public Gtk::CellRendererCombo {
private:
  Gtk::TreeModelColumn<Glib::ustring>   column_value_;
  Gtk::TreeModelColumnRecord            columns_;
  Glib::RefPtr<Gtk::ListStore>          liststore_;

public:
  inline CellRendererEncoding() :
    Glib::ObjectBase(0)
  {
    columns_.add(column_value_);
    liststore_ = Gtk::ListStore::create(columns_);

    const char *c[] = {"", "EUC-JP", "CP932", NULL};
    for(const char **p = c; *p; ++p) {
      (*liststore_->append())[column_value_] = *p;
    }

    property_model()        = liststore_;
    property_text_column()  = 0;
    property_has_entry()    = true;
  }
};

template<typename ET>
class CellRendererEnum : public Gtk::CellRendererCombo {
private:
  sigc::signal<void, const Glib::ustring &, ET> signal_changed_;
public:
  sigc::signal<void, const Glib::ustring &, ET> signal_changed() {
    return signal_changed_;
  }

private:
  typedef sigc::slot<Glib::ustring, int> SlotToStr;
  typedef sigc::slot<int, Glib::ustring> SlotToEnum;

  SlotToStr  tostr_;
  SlotToEnum toenum_;

  Gtk::TreeModelColumn<Glib::ustring>   column_name_;
  //Gtk::TreeModelColumn<ET>              column_value_;
  Gtk::TreeModelColumnRecord            columns_;
  Glib::RefPtr<Gtk::ListStore>          liststore_;

  static void static_on_edited(GtkCellRendererText */*renderer*/, gchar *path, gchar *new_text, gpointer user_data) {
    reinterpret_cast<CellRendererEnum<ET> *>(user_data)->on_edited(path, new_text);
  }

  virtual void on_edited(const Glib::ustring &path, const Glib::ustring &new_text) {
    signal_changed_(path, static_cast<ET>(toenum_(new_text)));
  }

public:
  void set_value(ET e) {
    property_text() = tostr_(e);
  }

  CellRendererEnum(ET begin, ET endn, SlotToStr tostr, SlotToEnum toenum) :
    Glib::ObjectBase(0),
    tostr_          (tostr),
    toenum_         (toenum)
  {
    columns_.add(column_name_);
    //columns_.add(column_value_);
    liststore_ = Gtk::ListStore::create(columns_);
    for(int e = begin; e < endn; ++e) {
      Gtk::TreeRow row = *liststore_->append();
      row[column_name_]  = tostr_(static_cast<ET>(e));
      //row[column_value_] = static_cast<ET>(e);
    }
    property_model()        = liststore_;
    property_text_column()  = 0;
    property_has_entry()    = false;

    g_signal_connect(gobj(), "edited", G_CALLBACK(&CellRendererEnum<ET>::static_on_edited), (gpointer)this);
  }
};
class CellRendererYPType : public CellRendererEnum<YPType> {
public:
  inline CellRendererYPType() :
    Glib::ObjectBase(0),
    CellRendererEnum<YPType>(YPTYPE_YP4G, YPTYPE_INVALID,
                             sigc::ptr_fun(&AttributeYPType::to_string),
                             sigc::ptr_fun(&AttributeYPType::to_value))
  {
  }
};

class TriCheckButton : public Gtk::CheckButton {
private:
  conf::RefAttributeTribool attr_;
  sigc::connection          conn_attr_changed_;

  virtual bool on_button_press_event(GdkEventButton *event);

  void attr_on_changed();

public:
  void set(conf::RefAttributeTribool attr);
  void unset();

  TriCheckButton(const Glib::ustring &label);
};

}/*pref*/
}/*gpyp*/

#endif/*PREF_WIDGETS_H_*/
